package com.zerocarbon.framework.core.jwt;

import cn.hutool.core.util.StrUtil;
import com.zerocarbon.framework.common.bo.TokenBO;
import com.zerocarbon.framework.common.constants.ContextConstants;
import com.zerocarbon.framework.common.enums.BaseExceptionCode;
import com.zerocarbon.framework.common.result.JwtCheckResult;
import com.zerocarbon.framework.common.utils.Charsets;
import com.zerocarbon.framework.common.utils.RsaUtils;
import com.zerocarbon.framework.common.utils.StrPool;
import com.zerocarbon.framework.core.exception.BusinessException;
import io.jsonwebtoken.*;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.security.Key;
import java.security.PrivateKey;
import java.util.Base64;
import java.util.Date;
import java.util.Map;

/**
 * JWT帮助类
 * @author wyf  2020年5月31日
 */
@Slf4j
public class JwtHelper {
	
	/**
     * 将 签名（JWT_SIGN_KEY） 编译成BASE64编码
     */
    //private static String BASE64_SECURITY = Base64.getEncoder().encodeToString(ContextConstants.JWT_SIGN_KEY.getBytes(Charsets.UTF_8));

    /**
     * authorization: Basic clientId:clientSec
     * 解析请求头中存储的 client 信息
     * Basic clientId:clientSec -截取-> clientId:clientSec后调用 extractClient 解码
     * @param basicHeader Basic clientId:clientSec
     * @return clientId:clientSec
     */
    /*public static String[] getClient(String basicHeader) {
        if (StrUtil.isEmpty(basicHeader) || !basicHeader.startsWith(BASIC_HEADER_PREFIX)) {
        	log.error("basicHeader is error, basicHeader:{}", basicHeader);
            throw BusinessException.wrap(BaseExceptionCode.TOKEN_INVALID_EX);
        }
        String decodeBasic = StrUtil.subAfter(basicHeader, BASIC_HEADER_PREFIX, false);
        return extractClient(decodeBasic);
    }*/
    
    /**
     * 解析请求头中存储的 client 信息
     * clientId:clientSec 解码
     */
    public static String[] extractClient(String client) {
        String token = base64Decoder(client);
        int index = token.indexOf(StrPool.COLON);
        if (index == -1) {
        	log.error("client is error, client:{}", client);
            throw BusinessException.wrap(BaseExceptionCode.TOKEN_INVALID_EX);
        } else {
            return new String[]{token.substring(0, index), token.substring(index + 1)};
        }
    }
    
    /**
     * 使用 Base64 解码
     * @param header
     * @return
     */
    @SneakyThrows
    public static String base64Decoder(String header) {
        byte[] decoded = Base64.getDecoder().decode(header.getBytes(Charsets.UTF_8_NAME));
        return new String(decoded, Charsets.UTF_8_NAME);
    }
    
    /**
     * 创建令牌
     * @param user   user
     * @param expire 过期时间（秒)
     * @return jwt
     */
    public static TokenBO createJWT(String id, Map<String, String> user, long expire, SignatureAlgorithm alg, String base64SecurityOrPrivateKey) {
		long nowMillis = System.currentTimeMillis();
		Date now = new Date(nowMillis);
		Key signingKey = null;
        if(alg!=null && SignatureAlgorithm.RS256.getValue().equals(alg.getValue())) {
        	signingKey = getKeyByRs256(base64SecurityOrPrivateKey);
        } else {
        	signingKey = getKeyByHs256(alg, base64SecurityOrPrivateKey);
        }
        //添加构成JWT的类
        JwtBuilder builder = Jwts.builder()
        		.setId(id)
        		.setHeaderParam(Header.TYPE, Header.JWT_TYPE)
        		.setHeaderParam("alg", alg.getValue())
        		.signWith(alg, signingKey); 					//签名算法以及密匙
        
        //设置JWT参数
        user.forEach(builder::claim);

        //添加Token过期时间
        long expMillis = nowMillis + expire * 1000;
        Date exp = new Date(expMillis);
        builder.setExpiration(exp).setNotBefore(now);		//过期时间

        // 组装Token信息
        TokenBO tokenInfo = new TokenBO();
        tokenInfo.setToken(builder.compact());				//生成JWT
        tokenInfo.setExpire(expire);
        tokenInfo.setExpiration(exp);
        return tokenInfo;
    }
    
    private static Key getKeyByHs256(SignatureAlgorithm signatureAlgorithm, String base64Security) {
    	if(StrUtil.isEmpty(base64Security))
    		base64Security = ContextConstants.JWT_SIGN_KEY;
        byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(base64Security);
        Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName()); //生成签名密钥
        return signingKey;
    }
    
    private static Key getKeyByRs256(String privateKeyBase64) {
    	if(StrUtil.isEmpty(privateKeyBase64))
    		privateKeyBase64 = ContextConstants.JWT_PRIVATE_KEY;
        PrivateKey privateKey = RsaUtils.getPrivateKey(privateKeyBase64); //生成签名密钥
        return privateKey;
    }
    
    private static String getDefaultParseJwtKey(SignatureAlgorithm alg) {
    	if(SignatureAlgorithm.RS256.getValue().equals(alg.getValue()))
    		return ContextConstants.JWT_PUBLIC_KEY;
    	else
    		return ContextConstants.JWT_SIGN_KEY; 
    }
    
    /**
	 * 解析验证token
	 * @param jsonWebToken
	 * @return
	 */
    public static Claims parseJWT(String jsonWebToken, SignatureAlgorithm alg, String base64SecurityOrPublicKey) {
    	try {
    		if(StrUtil.isEmpty(base64SecurityOrPublicKey))
    			base64SecurityOrPublicKey = getDefaultParseJwtKey(alg);
		    if(SignatureAlgorithm.RS256.getValue().equals(alg.getValue())) {
		    	return Jwts.parser()
			    		.setSigningKey(RsaUtils.getPublicKey(base64SecurityOrPublicKey))
			            .parseClaimsJws(jsonWebToken)
			            .getBody();
		    } else {
		    	return Jwts.parser()
			    		//.setSigningKey(Base64.getDecoder().decode(BASE64_SECURITY))
			    		.setSigningKey(DatatypeConverter.parseBase64Binary(base64SecurityOrPublicKey))
			            .parseClaimsJws(jsonWebToken)
			            .getBody();
		    }
    	} catch (ExpiredJwtException ex) { //过期
    		log.error("ExpiredJwtException errcode:{}, message:{}", BaseExceptionCode.TOKEN_EXPIRED_EX.getCode(), ex.getMessage());
            throw new BusinessException(BaseExceptionCode.TOKEN_EXPIRED_EX.getCode(), BaseExceptionCode.TOKEN_EXPIRED_EX.getMessage());
        } catch (SignatureException ex) {//签名错误
        	log.error("SignatureException errcode:{}, message:{}", BaseExceptionCode.TOKEN_SIGNATURE_EX.getCode(), ex.getMessage());
            throw new BusinessException(BaseExceptionCode.TOKEN_SIGNATURE_EX.getCode(), BaseExceptionCode.TOKEN_SIGNATURE_EX.getMessage());
        } catch (IllegalArgumentException ex) {//token 为空
        	log.error("IllegalArgumentException errcode:{}, message:{}", BaseExceptionCode.TOKEN_ILLEGAL_ARGUMENT_EX.getCode(), ex.getMessage());
            throw new BusinessException(BaseExceptionCode.TOKEN_ILLEGAL_ARGUMENT_EX.getCode(), BaseExceptionCode.TOKEN_ILLEGAL_ARGUMENT_EX.getMessage());
        } catch (Exception ex) {
        	log.error("Exception errcode:{}, message:{}", BaseExceptionCode.TOKEN_PARSER_FAIL_EX.getCode(), ex.getMessage());
            throw new BusinessException(BaseExceptionCode.TOKEN_PARSER_FAIL_EX.getCode(), BaseExceptionCode.TOKEN_PARSER_FAIL_EX.getMessage());
        }
    }

    /**
     * 获取token，除去Bearer 
     * @param token
     * @return
     */
    public static String getNoBearerToken(String token) {
    	if(StringUtils.isBlank(token)) {
            throw BusinessException.wrap(BaseExceptionCode.TOKEN_ILLEGAL_ARGUMENT_EX);
        }
        if (token.startsWith(ContextConstants.BEARER_HEADER_PREFIX)) {
            return StrUtil.subAfter(token, ContextConstants.BEARER_HEADER_PREFIX, false);
        }
        throw BusinessException.wrap(BaseExceptionCode.TOKEN_PARSER_FAIL_EX);
    }

    /**
     * 获取Claims
     * @param token
     * @return
     */
    public static Claims getClaims(String token, SignatureAlgorithm alg, String base64SecurityOrPublicKey) {
    	return parseJWT(getNoBearerToken(token), alg, base64SecurityOrPublicKey);
    }
    
    /**
     * 验证jwt
     * @param jwtStr
     * @param base64Security
     * @return
     */
	public static JwtCheckResult validateJWT(String jwtStr, SignatureAlgorithm alg, String base64SecurityOrPublicKey) {
		JwtCheckResult checkResult = new JwtCheckResult(false);
        Claims claims = null;
        //try {
            claims = parseJWT(jwtStr, alg, base64SecurityOrPublicKey);			//base64Security
            checkResult.setSuccess(true);
            checkResult.setClaims(claims);
        //} 
        /*catch (ExpiredJwtException e) {
			log.error("", e);
            checkResult.setErrorCode(JWT_ERRCODE_EXPIRE);
        } catch (SignatureException e) {
			log.error("", e);
            checkResult.setErrorCode(JWT_ERRCODE_FAIL);
        } catch (Exception e) {
			log.error("", e);
            checkResult.setErrorCode(JWT_ERRCODE_FAIL);
        } */
        return checkResult;
    }
	
	/*public static void main(String arg[]) {
		Map<String, String> map = new HashMap<>();
		map.put("test", "11111");
		TokenVO vo = JwtHelper.createJWT(map, 1000, SignatureAlgorithm.RS256, ContextConstants.JWT_PRIVATE_KEY);
		System.out.println(vo);
		
		Claims cls = JwtHelper.parseJWT(vo.getToken(), SignatureAlgorithm.RS256, ContextConstants.JWT_PUBLIC_KEY);
		System.out.println(cls.get("test"));
	}*/
}